﻿#region COPYRIGHT
//--------------------------------------------------------------------------------
// <copyright file="GUIManager.cs" company="starLiGHT Entertainment Studios">
//    Copyright (c) 2011 
//       Roland Rosenkranz (Glatzemann@email.de)
// </copyright>
// <license>
//   This file is part of starLiGHT.GUI.
//
//   It is released under the Microsoft Public License (Ms-PL).
//
//   You should have received a copy of the Microsoft Public License
//   along with starLiGHT.GUI. If not, see http://sle.codeplex.com/license.
//    
//   ADDITIONAL (commercial) LICENSES for starLiGHT.GUI are available on request.
// </license>
// <version>
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev::                       $:  Revision of last commit *
// * $Author::                    $:  Author of last commit   *
// * $Date::                      $:  Date of last commit     *
// ************************************************************
// </version>
//--------------------------------------------------------------------------------
#endregion

namespace starLiGHT.GUI
{
#region Using Statements
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using starLiGHT.Collections;
    using starLiGHT.GUI.Widgets;
#endregion

    public sealed class GuiManager
    {
        #region Member Variables

        private bool spritePointerEnabled;
        private Texture2D defaultSpritePointer;
        private Vector2 pointerPosition;
        private Rectangle screenRect;
        private BasicEffect effect;
        private VertexDeclaration vertexDeclaration;
        private WidgetCollection widgets;
        private MouseState lastMouseState;
        private IComparer<Widget> widgetComparer = new WidgetDepthComparer();
        private SkinXMLLoader skinLoader;
        private SpriteBatchRenderer renderer;
        private Skin skin;
        private Widget focusedWidget = null;
        private FastList<Widget> actHits = new FastList<Widget>(10);
        private FastList<Widget> prevHits = new FastList<Widget>(10);


        GraphicsDevice graphics;
        ContentManager content;
        #endregion

        public GuiManager(Game game, string skinFileName)
            : this(game.GraphicsDevice, game.Content, skinFileName)
        {
        }
        public GuiManager(GraphicsDevice graphics, ContentManager content, string skinFileName)
        {

            if (graphics == null)
            {
                throw new ArgumentNullException("graphics");
            }

            if (content == null)
            {
                throw new ArgumentNullException("content");
            }

            if (string.IsNullOrEmpty(skinFileName))
            {
                throw new ArgumentNullException("skinFileName", "a filename for the skin to be loaded is mandatory");
            }

            this.graphics = graphics;
            this.content = content;

            this.screenRect = graphics.Viewport.TitleSafeArea;
            this.widgets = new WidgetCollection(null);

            this.skinLoader = new SkinXMLLoader(skinFileName);
            this.renderer = new SpriteBatchRenderer(this, graphics);
        }

        public Matrix ViewMatrix
        {
            get
            {
                return renderer.ViewMatrix;
            }
            set
            {
                renderer.ViewMatrix = value;
            }
        }

        public bool IsScissoringEnabled
        {
            get
            {
                return renderer.IsScissoringEnabled;
            }
            set
            {
                renderer.IsScissoringEnabled = value;
            }
        }

        public Texture2D SpriteCursor
        {
            get
            {
                return this.defaultSpritePointer;
            }
        }

        public Widget ActiveWidget
        {
            get
            {
                return this.focusedWidget;
            }

            internal set
            {
                this.focusedWidget = value;
            }
        }

        public Vector2 PointerPosition
        {
            get
            {
                return this.pointerPosition;
            }
        }

        public bool SpriteCursorEnabled
        {
            get
            {
                return this.spritePointerEnabled;
            }

            set
            {
                this.spritePointerEnabled = value;

                if (this.spritePointerEnabled && this.defaultSpritePointer == null)
                {
                    this.defaultSpritePointer = Content.Load<Texture2D>(@"Textures/Default_SpriteCursor");
                }
                else if (!this.spritePointerEnabled && this.defaultSpritePointer != null)
                {
                    this.defaultSpritePointer.Dispose();
                    this.defaultSpritePointer = null;
                }
            }
        }

        public Skin Skin
        {
            get
            {
                return this.skin;
            }
        }

        public SpriteFont DefaultFont
        {
            get
            {
                return this.skin.DefaultFont;
            }
        }

        public WidgetCollection Widgets
        {
            get
            {
                return this.widgets;
            }
        }


        public GraphicsDevice GraphicsDevice
        {
            get
            {
                return graphics;
            }
        }

        public ContentManager Content
        {
            get
            {
                return content;
            }
        }

        public void LoadGUI(string guiContentAssetName)
        {
            GUIContent gui = Content.Load<GUIContent>(guiContentAssetName);
            this.LoadGUI(gui);
        }

        public void LoadGUI(GUIContent gui)
        {
            foreach (Window w in gui.Windows)
            {
                this.widgets.Add(w);
            }
        }

        public Window CreateWindow(string name)
        {
            Window w = new Window(this, name);
            this.widgets.Add(w);
            return w;
        }

        public Window CreateWindow(string name, string title)
        {
            Window w = new Window(this, name, title);
            this.widgets.Add(w);
            return w;
        }

        public bool HasWindow(string name)
        {
            foreach (Window w in this.widgets)
            {
                if (w.Name == name)
                {
                    return true;
                }
            }

            return false;
        }

        public IEnumerable<Widget> HitWidgets(params Vector2[] position)
        {
            foreach (Widget w in this.Widgets)
            {
                if (!w.Visible)
                {
                    continue;
                }

                foreach (Vector2 v in position)
                {
                    if (w.HitTest(v))
                    {
                        yield return w;
                        break;
                    }
                }
            }
        }

        public void LoadSkin()
        {
            ////defaultFont = Game.Content.Load<SpriteFont>(@"Fonts/Default");

            this.skin = this.skinLoader.Load(this);
        }
		
        public void LoadSkin(System.IO.Stream xDocStream) // workaround to support loading with MonoGame
        {
            this.skin = this.skinLoader.Load(this, xDocStream);
        }
		
        public void UnloadContent()
        {
            if (this.defaultSpritePointer != null)
            {
                this.defaultSpritePointer.Dispose();
                this.defaultSpritePointer = null;
            }
        }

        public void Draw(GameTime gameTime)
        {
             this.renderer.Draw(gameTime);
        }

        public void Update(GameTime gameTime)
        {
            MouseState ms = Mouse.GetState();

            if (ViewMatrix != Matrix.Identity || GraphicsDevice.Viewport.X != 0 || GraphicsDevice.Viewport.Y != 0)
            {
                Vector2 pos = ms.GetPosition();

                pos -= new Vector2(GraphicsDevice.Viewport.X, GraphicsDevice.Viewport.Y);
                pos = Vector2.Transform(pos, Matrix.Invert(ViewMatrix));


                ms = new MouseState((int)pos.X, (int)pos.Y, ms.ScrollWheelValue, ms.LeftButton, ms.MiddleButton, ms.RightButton, ms.XButton1, ms.XButton2);
            }

            Update(gameTime, ms);

        }
            
        public void Update(GameTime gameTime, MouseState ms)
        {

            this.pointerPosition = ms.GetPosition();
            //this.pointerPosition.X = MathHelper.Clamp(this.pointerPosition.X, this.screenRect.Left, this.screenRect.Right);
            //this.pointerPosition.Y = MathHelper.Clamp(this.pointerPosition.Y, this.screenRect.Top, this.screenRect.Bottom);

            bool leftMouseDown = ms.LeftButton == ButtonState.Pressed && this.lastMouseState.LeftButton == ButtonState.Released;
            bool rightMouseDown = ms.RightButton == ButtonState.Pressed && this.lastMouseState.RightButton == ButtonState.Released;
            bool leftMouseUp = ms.LeftButton == ButtonState.Released && this.lastMouseState.LeftButton == ButtonState.Pressed;
            bool rightMouseUp = ms.RightButton == ButtonState.Released && this.lastMouseState.RightButton == ButtonState.Pressed;

            if (ms.X != this.lastMouseState.X || ms.Y != this.lastMouseState.Y || leftMouseDown || leftMouseUp || rightMouseDown || rightMouseUp)
            {
                //foreach (Widget w in this.widgets)
                //{
                //    w.OnGlobalMouseMove(w, new MouseEventArgs(ms.LeftButton == ButtonState.Pressed, ms.RightButton == ButtonState.Pressed, this.pointerPosition));
                //}

                // list all widgets that are actually under the mouse pointer
                this.actHits.Clear();
                foreach (Widget w in this.HitWidgets(this.pointerPosition))
                {
                    this.actHits.Add(w);
                }

                this.actHits.Sort(this.widgetComparer);

                if (this.focusedWidget != null && !this.actHits.Contains(this.focusedWidget))
                {
                    this.HandleMouseEvents(this.focusedWidget, ms, leftMouseDown, rightMouseDown, leftMouseUp, rightMouseUp, false);
                }

                // MouseEnter occurs once, when the mouse pointer enters the DisplayRectangle of the widget
                // MouseMove occurs when the pointer is moved above a widget
                // MouseDown occurs when the pointer is over a widget and a mouse button is pressed
                // MouseUp occurs when the pointer is over a widget and a mouse button is released
                foreach (Widget w in this.actHits)
                {
                    if (!w.Visible)
                    {
                        continue;
                    }

                    if (this.HandleMouseEvents(w, ms, leftMouseDown, rightMouseDown, leftMouseUp, rightMouseUp, true))
                    {
                        break;
                    }
                }

                // MouseLeave occurs once, when the mouse pointer leaves the DisplayRectangle of the widget
                for (int i = this.prevHits.Count - 1; i >= 0; i--)
                {
                    Widget w = this.prevHits[i];
                    if (!w.Visible)
                    {
                        continue;
                    }

                    if (!this.actHits.Contains(w))
                    {
                        ////Trace.WriteLine("MouseLeave: " + w.Name);
                        w.OnMouseLeave(w, EventArgs.Empty);
                        this.prevHits.Remove(w);
                    }
                }
            }

            // Update widgets like the TextBox
            if (this.focusedWidget != null && this.focusedWidget is IUpdateableWidget)
            {
                ((IUpdateableWidget)this.focusedWidget).Update(gameTime);
            }

            this.lastMouseState = ms;
        }

        public void BringToFront(Widget w)
        {
            this.widgets.Remove(w);
            this.widgets.Add(w);
        }

        public void BringToFront(ContainerWidget container, Widget w)
        {
            container.Widgets.Remove(w);
            container.Widgets.Add(w);
        }

        public void SendToBack(Widget w)
        {
            this.widgets.Remove(w);
            this.widgets.Insert(0, w);
        }

        public T LoadContent<T>(string assetName)
        {
            return Content.Load<T>(assetName);
        }

        private bool HandleMouseEvents(Widget w, MouseState ms, bool leftMouseDown, bool rightMouseDown, bool leftMouseUp, bool rightMouseUp, bool widgetUnderMouse)
        {
            if (!this.prevHits.Contains(w))
            {
                ////Trace.WriteLine("MouseEnter: " + w.Name);
                w.OnMouseEnter(w, new MouseEventArgs(ms.LeftButton == ButtonState.Pressed, ms.RightButton == ButtonState.Pressed, this.pointerPosition, widgetUnderMouse));
                this.prevHits.Add(w);
            }
            else
            {
                ////Trace.WriteLine("MouseMove: " + w.Name);
                w.OnMouseMove(w, new MouseEventArgs(ms.LeftButton == ButtonState.Pressed, ms.RightButton == ButtonState.Pressed, this.pointerPosition, widgetUnderMouse));
            }

            if (leftMouseDown || rightMouseDown)
            {
                ////Trace.WriteLine("MouseDown: " + w.Name);
                w.OnMouseDown(w, new MouseEventArgs(leftMouseDown, rightMouseDown, this.pointerPosition, widgetUnderMouse));

                if (w.CanFocus && leftMouseDown)
                {
                    w.SetFocus();
                    return true;
                }
            }

            if (leftMouseUp || rightMouseUp)
            {
                ////Trace.WriteLine("MouseUp: " + w.Name);
                w.OnMouseUp(w, new MouseEventArgs(leftMouseUp, rightMouseUp, this.pointerPosition, widgetUnderMouse));
            }

            return false;
        }
    }
}
